package chaos.util;

/** Hexadecimal numbers manipulation class.
  *
  * @author memming
  */
public class Hex {
	/** All methods are called static. no instance needed. */
	private Hex() {}

	/** Hexadecimal table for fast converting. */
	private static final char[] hexDigits =
		{ '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' };

	/** Static function to dump a single byte to hexa. */
	public static String dumpHex(byte hex)	{
		char[] hexChar = new char[2];
		hexChar[0] = hexDigits[(hex >>> 4) & 0x0F];
		hexChar[1] = hexDigits[ hex        & 0x0F];

		return new String(hexChar);
	}

	/** Append 2 charactors of hex's hexadecimal value
	 * at the end of StringBuffer.
	 * @param hex byte to be appended
	 * @param str StringBuffer to append to
	 */
	public static void appendHex(byte hex, StringBuffer str)	{
		str.append((char)hexDigits[(hex >>> 4) & 0x0F]);
		str.append((char)hexDigits[ hex        & 0x0F]);
	}

	/** Dumps a byte array to hexadecimally represented numbers.
	 * @param strBuffer string buffer to append to
	 * @param byteArray array to be dumped
	 * @param offset offset of the array to start dumpping
	 * @param length length of the input data in the array
	 * @param separator byte separating charactor (0x00 for no separator)
	 */
	public static void dumpHex(StringBuffer strBuffer, byte[] byteArray,
		int offset, int length, char separator)
	{
		if(byteArray == null || byteArray.length == 0) return;
		for(int i=offset;i<offset+length;i++) {
			appendHex(byteArray[i], strBuffer);
			if(separator != (char)0x00 && i != offset+length-1) {
				strBuffer.append(separator);
			}
		}
	}

	/** same as dumpHex but puts line break at given column.
	 * @param strBuffer string buffer to append to
	 * @param byteArray array to be dumped
	 * @param offset offset of the array to start dumpping
	 * @param length length of the input data in the array
	 * @param column maximun column to break line
	 * @param separator byte separating charactor (0x00 for no separator)
	 * @param indent indent each line by <code>indent</code>*4
	 */
	public static void prettyDump(StringBuffer sb, byte[] byteArray,
			int offset, int length, int column, char separator, int indent)
	{
		int blockSize;
		if(separator == (char)0x00) { // no separator 2 * bytes
			blockSize= column/2;
		} else {
			blockSize= column/3;
		}
		int leftOvers = length % blockSize;
		int lastBlockOffset = length - leftOvers;

		for(int i=0; i<lastBlockOffset ; i+=blockSize) {
			if ( indent > 0 ) StringUtil.indent(sb,indent);
			dumpHex(sb, byteArray, offset+i, blockSize, separator);
			sb.append("\n");
		}
		if ( indent > 0 ) StringUtil.indent(sb,indent);
		dumpHex(sb, byteArray, lastBlockOffset, leftOvers, separator);
	}


	/** same as dumpHex but puts line break at given column.
	 * @param strBuffer string buffer to append to
	 * @param byteArray array to be dumped
	 * @param offset offset of the array to start dumpping
	 * @param length length of the input data in the array
	 * @param column maximun column to break line
	 * @param separator byte separating charactor (0x00 for no separator)
	 */
	public static void prettyDump(StringBuffer sb, byte[] byteArray,
			int offset, int length, int column, char separator)
	{
		prettyDump(sb,byteArray,offset,length,column,separator,0);
	}

	/** Dumps a byte array to hexadecimally represented numbers.
	 * @param byteArray array to be dumped
	 * @param offset offset of the array to start dumpping
	 * @param length length of the input data in the array
	 * @param separator byte separating charactor (0x00 for no separator)
	 * @return alphanumerical readable hexadecimal string
	 */
	public static String dumpHex(byte[] byteArray, int offset, int length,
			char separator)
	{
		StringBuffer sb = new StringBuffer();
		dumpHex(sb, byteArray, offset, length, separator);
		return sb.toString();
	}

	/** Dumps a byte array to hexadecimally represented numbers.
	 * @param byteArray array to be dumped
	 * @param separator byte separating charactor (0x00 for no separator)
	 * @return alphanumerical readable hexadecimal string
	 */
	public static String dumpHex(byte[] byteArray, char separator)
	{
		return dumpHex(byteArray, 0, byteArray.length, separator);
	}

	/** Dumps a byte array to hexadecimally represented numbers with no
	 * separator.
	 * @param byteArray array to be dumped
	 * @return alphanumerical readable hexadecimal string
	 */
	public static String dumpHex(byte[] byteArray)
	{
		return dumpHex(byteArray, (char)0x00);
	}

	/** same as dumpHex but puts line break at given column.
	 * @param byteArray array to be dumped
	 * @param offset offset of the array to start dumpping
	 * @param length length of the input data in the array
	 * @param column maximun column to break line
	 * @param separator byte separating charactor (0x00 for no separator)
	 * @param indent indentation
	 * @return alphanumerical readable hexadecimal string
	 */
	public static String prettyDump(byte[] byteArray,
			int offset, int length, int column, char separator, int indent)
	{
		StringBuffer sb = new StringBuffer();
		prettyDump(sb, byteArray, offset, length, column, separator, indent);
		return sb.toString();
	}

	/** same as dumpHex but puts line break at given column.
	 * @param byteArray array to be dumped
	 * @param offset offset of the array to start dumpping
	 * @param length length of the input data in the array
	 * @param column maximun column to break line
	 * @param separator byte separating charactor (0x00 for no separator)
	 * @return alphanumerical readable hexadecimal string
	 */
	public static String prettyDump(byte[] byteArray,
			int offset, int length, int column, char separator)
	{
		StringBuffer sb = new StringBuffer();
		prettyDump(sb, byteArray, offset, length, column, separator,0);
		return sb.toString();
	}

	/** same as dumpHex but puts line break at given column.
	 * @param byteArray array to be dumped
	 * @param column maximun column to break line
	 * @param separator byte separating charactor (0x00 for no separator)
	 * @param indent indentation
	 * @return alphanumerical readable hexadecimal string
	 */
	public static String prettyDump(byte[] byteArray,
			int column, char separator, int indent)
	{
		return prettyDump(byteArray,0,byteArray.length,column,separator,indent);
	}


	/** same as dumpHex but puts line break at given column.
	 * @param byteArray array to be dumped
	 * @param column maximun column to break line
	 * @param separator byte separating charactor (0x00 for no separator)
	 * @return alphanumerical readable hexadecimal string
	 */
	public static String prettyDump(byte[] byteArray,
			int column, char separator)
	{
		return prettyDump(byteArray, 0,  byteArray.length, column, separator,0);
	}

	/** same as dumpHex but puts line break at given column with default 80
	 * column and no separator.
	 * @param byteArray array to be dumped
	 * @return alphanumerical readable hexadecimal string
	 */
	public static String prettyDump(byte[] byteArray)
	{
		return prettyDump(byteArray, 0,  byteArray.length, 80, (char)0x00);
	}

	/** same as dumpHex but puts line break at given column with default 80
	 * column and no separator.
	 * Writes alphanumerical readable hexadecimal string to the PrintWriter
	 * @param out PrintWriter to write the output to
	 * @param byteArray array to be dumped
	 */
	public static void dumpHex(java.io.PrintWriter out, byte[] byteArray)
	{
		out.print(prettyDump(byteArray));
	}

	/** Parses hexastring to a byte array. Ignores non-hexadecimal charactors.
	  * @param hexString hexadecimal string to be parsed
	  * @return byte array that is parsed. if nothing is parsed then an empty
	  * byte array, that is a byte array with length zero will be returned.
	 */
	public static byte[] parseHexaString(String hexString) {
		StringBuffer sb = new StringBuffer();
		// First filter charactors to sb
		char current;
		int i;

		for(i=0; i<hexString.length(); i++) {
			current = hexString.charAt(i);
			if(('0' <= current && current <= '9') ||
			   ('A' <= current && current <= 'F') ||
			   ('a' <= current && current <= 'f')) {
				sb.append(current);
			}
		}

		int leftover = sb.length() % 2;
		byte[] ret = new byte[sb.length()/2+leftover];
		if(leftover == 1) { // odd number of half hexadecimals
			ret[0] = parseHexaCharactor(sb.charAt(0));
			for(i=0; i<sb.length()/2; i++) {
				ret[i+1] = (byte)((parseHexaCharactor(sb.charAt(2*i+1)) << 4) |
								(parseHexaCharactor(sb.charAt(2*i+2))));
			}
		} else {
			for(i=0 ;i<sb.length()/2; i++) {
				ret[i] = (byte)((parseHexaCharactor(sb.charAt(2*i)) << 4) |
								(parseHexaCharactor(sb.charAt(2*i+1))));
			}
		}

		return ret;
	}

	/** Parse a single charactor to 4bits, a half byte. */
	private static byte parseHexaCharactor(char ch) {
		if('0' <= ch && ch <= '9') {
			return (byte)(ch - 48);
		} else if('A' <= ch && ch <= 'F') {
			return (byte)(ch - 55);
		} else if('a' <= ch && ch <= 'f') {
			return (byte)(ch - 87);
		} else {
			return (byte)0x00;
		}
	}

// Selft test codes
	/*@begin_debug@
	public static void main(String[] args)
	throws Exception
	{
		System.out.println("0x0: "+dumpHex(parseHexaString("0")));
		System.out.println("0xf: "+dumpHex(parseHexaString("f")));
		System.out.println("0x1000: "+dumpHex(parseHexaString("1000")));
		System.out.println("0xffff: "+dumpHex(parseHexaString("ffff")));
		System.out.println("0xAAa: "+dumpHex(parseHexaString("AAa")));
		System.out.println("0xfFfFf: "+dumpHex(parseHexaString("fFfFf")));
		System.out.println("0x9876543210FEDCBA: " +
			dumpHex(parseHexaString("9876543210FEDCBA")));
	}
	@end_debug@*/
}
